package kt.smpp;

import ie.omk.smpp.Address;
import ie.omk.smpp.AlreadyBoundException;
import ie.omk.smpp.BadCommandIDException;
import ie.omk.smpp.Connection;
import ie.omk.smpp.SMPPException;
import ie.omk.smpp.UnsupportedOperationException;
import ie.omk.smpp.event.ReceiverExitEvent;
import ie.omk.smpp.event.SMPPEventAdapter;
import ie.omk.smpp.message.BindResp;
import ie.omk.smpp.message.DeliverSM;
import ie.omk.smpp.message.EnquireLink;
import ie.omk.smpp.message.EnquireLinkResp;
import ie.omk.smpp.message.InvalidParameterValueException;
import ie.omk.smpp.message.SMPPPacket;
import ie.omk.smpp.message.SMPPProtocolException;
import ie.omk.smpp.message.SubmitSM;
import ie.omk.smpp.message.SubmitSMResp;
import ie.omk.smpp.message.Unbind;
import ie.omk.smpp.message.UnbindResp;
import ie.omk.smpp.message.tlv.Tag;
import ie.omk.smpp.util.GSMConstants;
import ie.omk.smpp.util.UCS2Encoding;
import ie.omk.smpp.version.VersionException;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import kt.dal.DAL;
import kt.processor.MillionareUnlimitProcessor;
import kt.smpp.observer.AsyncObserver;
import kt.smpp.pdu.SMSRequest;
import kt.smpp.pdu.SMSResponse;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;

/**
 * Example SMPP receiver using asynchronous communications. This example
 * demonstrates asynchronous communications using the SMPPEventAdapter. The
 * SMPPEventAdapter is a utility class which implements the ConnectionObserver
 * interface for you and delivers received events to appropriate methods in the
 * adapter implementation.
 * 
 * @see ie.omk.smpp.examples.ParseArgs ParseArgs for details on running this
 *      class.
 */
public class MOReceiver {// extends Task { //TODO: do we need extend Task???

    private Logger logger = LogManager.getLogger(MOReceiver.class);
    int startSEQ_NO = 2;
    protected Connection smscConnection = null;
    protected SmppParameter parameters = null;
    AsyncObserver observer = null;
    MillionareUnlimitProcessor millionaireProcessor = new MillionareUnlimitProcessor();
    // Number of deliver_sm packets received
    
    public MOReceiver(SmppParameter params) {
    	//this.smscConnection = smscCnn;
    	this.parameters = params;
    }

    public void execute() throws BuildException, InvalidParameterValueException, AlreadyBoundException, VersionException, SMPPProtocolException, IllegalArgumentException, IOException, InterruptedException {
       
    	this.smscConnection = new Connection(parameters.hostName, parameters.port, true);
            // Create the observer
		observer = new AsyncObserver();		

		// set the receiver to automatically acknowledge deliver_sm and
		// enquire_link requests from the SMSC.
		smscConnection.autoAckLink(true);
		smscConnection.autoAckMessages(true);

		// add this example to the list of observers on the receiver
		// connection
		smscConnection.addObserver(observer);

		// bind to the SMSC as a receiver
		logger.info("Binding to the SMSC...");

		synchronized (this) {
			smscConnection.bind(Connection.TRANSCEIVER, parameters.systemID,
					parameters.password, parameters.systemType,
					parameters.sourceTON, parameters.sourceNPI,
					parameters.sourceAddress);
			wait();
		}

		// Close down the network connection.
		smscConnection.enquireLink();
		logger.info("Close the SMSC link " + this.parameters);
		smscConnection.closeLink();       
    }
    
    private void restart() {
    	//
    	//this.smscConnection.reset();
    	this.smscConnection.removeObserver(this.observer);
    	try {
			this.execute();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			logger.error("can not restart the connection to SMSC "  + this.parameters, e);
		}
    }
    
    private void saveDeliverSM(DeliverSM sm) {
    	SMSRequest req = new SMSRequest(sm, this.parameters.operator);
    	if(this.parameters.processImmediately) {
    		logger.info("Process immediately the SM " + sm);
    		ArrayList<SMSResponse> responses = this.millionaireProcessor.processRequest(req);
    		logger.info("send response to Customer");
    		// TODO need to implement
    		DAL.saveMOtoLog(req);
    		DAL.saveMT(responses);
    	} else {
    		logger.info("save the request to process later " + sm);
    		DAL.saveMO(req);
    	}
    }
    
    private boolean isSending = false;
	private void sendMT() {
		//Get all MT
		synchronized (this) {
			if(isSending) {
				return;
			}
		}
		synchronized (this) {
			isSending = true;
			List<SMSResponse> responses = DAL.getMT(MOReceiver.this.parameters.operator, 10000);
			//send each MT 1. send to SMSC 2. remove from MT table 3. remove from MO table 4. Save log to MT_LOG/MO_LOG
			for (SMSResponse smsResponse : responses) {
				sendMT2SMSC(smsResponse);				
			}			
			isSending = false;
		}
	}

	private void sendMT2SMSC(SMSResponse smsResponse) {
		// 1. update DB status 2. send to SMSC
		try {
            Address destination = new Address(this.parameters.sourceTON, 
            		this.parameters.sourceNPI, smsResponse.getSenderNumber());

            SubmitSM sm = (SubmitSM) this.smscConnection.newInstance(SMPPPacket.SUBMIT_SM);
            sm.setDestination(destination);
            sm.setSource(new Address(GSMConstants.GSM_TON_UNKNOWN,
                    GSMConstants.GSM_NPI_UNKNOWN, smsResponse.getServiceNumber()));
            DAL.updateMTStatusAndSeqById(smsResponse.getId(), sm.getSequenceNum(), SMSResponse.DeliveryStatus.SUBMITTING);
            
//            if(!smsResponse.isUnicode()) {
//            	if(smsResponse.getContentType() == SMSResponse.CT_WAP_SI) {
//            		byte[] content = createWappush("your result", smsResponse.getInfo());
//            		sm.setMessage(content);
//            		sm.setEsmClass(64);
//            	} else {
//            		sm.setMessageText(smsResponse.getInfo());
//            	}
//            } else {
//            	//TODO send unicode
//            	logger.info("send unicode message " + smsResponse.getInfo());
//            	//sm.setMessageText(smsResponse.getInfo(), new UCS2Encoding());
//            	sendUnicode(smsResponse.getInfo(), sm.getSource(), sm.getDestination(), sm.getSequenceNum());
//            }
            
            if(logger.isDebugEnabled()){
            	logger.debug("submit message: " + sm);
            }
            sm.setMessageText(smsResponse.getInfo());
            logger.info("submit message: " + sm);
            smscConnection.sendRequest(sm);
        } catch (IOException x) {
            logger.error("I/O Exception", x);
        } catch (SMPPException x) {
            logger.error("SMPP Exception", x);
        }
	}
	
	private void sendUnicode(String msg, Address from, Address to, int seqNo) throws VersionException, BadCommandIDException, SocketTimeoutException, AlreadyBoundException, SMPPProtocolException, UnsupportedOperationException, IOException{
		UCS2Encoding alphabet = new UCS2Encoding();
		int maxShortMessageLength = 134;
		int maxSegmentLength = 134;
		byte[] binaryContent = alphabet.encodeString(msg);
		int parts = (binaryContent.length < maxShortMessageLength ? 1
				: (int) Math.ceil(((double) binaryContent.length)
						/ maxSegmentLength));
		Integer msgRefNum = new Integer(getNextRefNum());
		SubmitSM sm;
		if (parts == 1) {	
			sm = (SubmitSM) this.smscConnection.newInstance(SMPPPacket.SUBMIT_SM);
			sm.setMessage(binaryContent, alphabet);
			sm.setSource(from);
			sm.setDestination(to);
			this.smscConnection.sendRequest(sm);
		} else {
			byte[] packet = new byte[maxSegmentLength];
			for (int i = 0; i < parts; i++) {
				sm = (SubmitSM) this.smscConnection.newInstance(SMPPPacket.SUBMIT_SM);
				sm.setSequenceNum(seqNo + i);
				sm.setSource(from);
				sm.setDestination(to);
				sm.setOptionalParameter(Tag.SAR_TOTAL_SEGMENTS, new Integer(parts));
				sm.setOptionalParameter(Tag.SAR_MSG_REF_NUM, msgRefNum);
				sm.setOptionalParameter(Tag.SAR_SEGMENT_SEQNUM, new Integer(i + 1));
				if (i == parts - 1) {
					int pos = i * maxSegmentLength;
					int len = binaryContent.length - pos;
					byte[] finalPacket = new byte[len];
					System.arraycopy(binaryContent, pos, finalPacket, 0, len);
					sm.setMessage(finalPacket, alphabet);
				} else {
					System.arraycopy(binaryContent, i * maxSegmentLength,
							packet, 0, maxSegmentLength);
					sm.setMessage(packet, alphabet);
				}
				logger.info("Sending Submit Packet: " + sm.getMessageText());
				this.smscConnection.sendRequest(sm);
			}
		}        
	}
	
    private int nextRefNum = 0; 
    private synchronized int getNextRefNum () { 
    	if (nextRefNum>=65535) { //2 byte positive integer
    		nextRefNum = 0; 
    	} 
    	return ++nextRefNum; 
    }
	
//	private SubmitSM createPacket(String receiver2) throws VersionException, BadCommandIDException {
//		SubmitSM sm = (SubmitSM) this.smscConnection.newInstance(SMPPPacket.SUBMIT_SM);
//        sm.setDestination(new Address(0, 0, "099296235"));
//        sm.setSource(new Address(0, 0, "609"));
//        return sm;
//	}

	public byte[] createWappush(String content, String url){
		byte hrefTagToken=0;
		if (url.startsWith("http://")) {
			if (url.startsWith("www.", 7)) {
				hrefTagToken = 0xD;
				url = url.substring(11);
			} else {
				hrefTagToken = 0xC;
				url = url.substring(7);
			}
		} else if (url.startsWith("https://")) {
			if (url.startsWith("www.", 8)) {
				hrefTagToken = 0xF;
				url = url.substring(12);
			} else {
				hrefTagToken = 0xE;
				url = url.substring(8);
			}
		}
		byte[] contents = createContent(content, url, hrefTagToken);
		return contents;
	}
	
	public static byte[] createContent(String text, String url,
			byte hrefTagToken) {
		ByteArrayOutputStream msg = new ByteArrayOutputStream();

		// build WDP header (UDH)
		msg.write(0x06);
		msg.write(0x05);
		msg.write(0x04);
		msg.write(0x0b);
		msg.write(0x84);
		msg.write(0x23);
		msg.write(0xf0);

		// build PDU
		byte[] pduBody = getPduBody(text, url, hrefTagToken);

		// Write WSP header
		msg.write(0x25);
		msg.write(0x06);
		msg.write(0x08); // the length of the next 8 bytes
		msg.write(0x03);
		msg.write(0xAE);
		msg.write(0x81);
		msg.write(0xEA);
		msg.write(0xaf);
		msg.write(0x82);
		msg.write(0xB4);
		msg.write(0x84);
		msg.write(pduBody, 0, pduBody.length);

		return msg.toByteArray();
	}

	private static byte[] getPduBody(String text, String url, byte hrefTagToken) {
		try {
			ByteArrayOutputStream pdu = new ByteArrayOutputStream();
			OutputStreamWriter pduWriter = new OutputStreamWriter(pdu, "UTF-8");
			pdu.write(0x01); // Version 1.1
			pdu.write(0x05); // ServiceIndication 1.0
			pdu.write(0x6A); // UTF-8
			pdu.write(0x00);
			pdu.write(SetTagTokenIndications((byte) 0x5, false, true)); // <si>
			pdu.write(SetTagTokenIndications((byte) 0x6, true, true)); // <indication
																		// href=...
																		// action=...>
			pdu.write(hrefTagToken); // href=
			pdu.write(0x03); // Inline string follows
			pduWriter.write(url);
			pduWriter.flush();
			pdu.write(0x00);
			pdu.write(0x07); // Action="signal-medium"
			pdu.write(0x01); // >
			pdu.write(0x03); // Inline string follows
			int maxTextLen = 119 - pdu.size();
			int charsToWrite = charsInTruncatedString(text, maxTextLen);
			pduWriter.write(text, 0, charsToWrite);
			pduWriter.flush();
			pdu.write(0x00);
			pdu.write(0x01); // </indication>
			pdu.write(0x01); // </si>

			return pdu.toByteArray();
		} catch (IOException e) {
			// There is no sane reason for us to get here.
			return null;
		}
	}

	// Returns the number of characters from the string that
	// fit into byteLength bytes, when encoded in UTF-8.
	private static int charsInTruncatedString(String string, int byteLimit)
			throws UnsupportedEncodingException {
		byte[] byteArray = string.getBytes("UTF-8");
		if (byteLimit >= byteArray.length)
			return string.length();
		int charCounter = 0, curr = 0;
		while (curr <= byteLimit) {
			charCounter++;
			if ((byteArray[curr] & 0x80) == 0x0)
				curr++;
			else if ((byteArray[curr] & 0xE0) == 0xC0)
				curr += 2;
			else if ((byteArray[curr] & 0xF0) == 0xE0)
				curr += 3;
			else
				curr += 4;
		}
		return (charCounter - 1);
	}

	private static byte SetTagTokenIndications(byte token,
			boolean hasAttributes, boolean hasContent) {
		if (hasAttributes)
			token |= 0xC0;
		if (hasContent)
			token |= 0x40;

		return token;
	}

}
